#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
作者: jena 
创建时间: 2025.9
字符清洗算子 - 离线环境优化版本
"""
import re
import unicodedata
import warnings
from typing import Dict, List, Union, Optional

# 导入配置和工具
try:
    from src.common_utils import safe_import
    from src.config import CharacterCleanerConfig
except ImportError:
    from .common_utils import safe_import
    from .config import CharacterCleanerConfig

# 离线环境依赖导入 - 统一管理
class DependencyManager:
    """依赖管理器 - 统一处理第三方库导入"""
    
    def __init__(self):
        self.bs4 = None
        self.opencc_converter = None
        self._init_dependencies()
    
    def _init_dependencies(self):
        """初始化依赖库"""
        # BeautifulSoup4 导入
        bs4_module, bs4_error = safe_import('bs4', 'beautifulsoup4', 
                                           "BeautifulSoup4不可用，HTML清理将使用正则表达式回退方案")
        if bs4_module:
            try:
                from bs4 import BeautifulSoup
                self.bs4 = BeautifulSoup
            except ImportError:
                warnings.warn("BeautifulSoup导入失败，使用回退方案")
        elif bs4_error:
            warnings.warn(bs4_error)
        
        # OpenCC 导入 - 只从offline_packages导入
        opencc_module, opencc_error = safe_import('opencc', 'opencc-python-reimplemented',
                                                 "OpenCC不可用，繁简转换功能将被跳过")
        if opencc_module:
            try:
                # 直接使用safe_import返回的模块，不再重新导入
                self.opencc_converter = opencc_module.OpenCC('t2s')  # 繁体转简体
            except Exception as e:
                warnings.warn(f"OpenCC初始化失败: {e}")
        elif opencc_error:
            warnings.warn(opencc_error)
    
    def has_bs4(self) -> bool:
        """检查BeautifulSoup4是否可用"""
        return self.bs4 is not None
    
    def has_beautifulsoup(self) -> bool:
        """检查BeautifulSoup4是否可用（别名方法）"""
        return self.has_bs4()
    
    def has_opencc(self) -> bool:
        """检查OpenCC是否可用"""
        return self.opencc_converter is not None
    
    def get_bs4(self):
        """获取BeautifulSoup类"""
        return self.bs4
    
    def get_opencc_converter(self):
        """获取OpenCC转换器"""
        return self.opencc_converter

# 全局依赖管理器实例
_deps = DependencyManager()

class CharacterCleaner:
    """
    ========== 字符级别的数据清洗 ==========
    
    【文本标准化处理】- 确保文本格式统一性和可读性
    1. 不可见字符清理 (remove_invisible_chars)
       • 移除零宽字符：\u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
       • 清理控制字符：\u0000-\u0008, \u000B, \u000C, \u000E-\u001F, \u007F-\u009F
       • 处理BOM标记和不间断空格：\uFEFF, \u00A0
       • 保留制表符、换行符、回车符和普通空格
    
    2. Unicode标准化 (unicode_normalize)
       • NFC标准化形式转换，统一字符编码
       • 解决组合字符显示不一致问题
       示例："café" (组合字符) → "café" (标准化字符)
       
    3. 繁体转简体 (convert_traditional_to_simplified)
       • 基于OpenCC的智能繁简转换
       • 支持地区化词汇转换（港澳台、大陆）
       • 安全导入机制，依赖库不可用时跳过处理
       
    【内容格式清理】- 移除标记语言和格式化内容（繁体转简体后处理）
    4. HTML标签清理 (remove_html_tags)
       • 基于BeautifulSoup的安全HTML解析
       • 保留文本内容，移除所有标签和属性
       • 处理HTML实体：&lt; &gt; &amp; &quot; &apos; &nbsp; &copy; &reg; &trade;
       • BeautifulSoup不可用时使用正则表达式回退方案
   
    5. Markdown格式清理 (remove_markdown_syntax)
       • 移除Markdown标题标记：# ## ### 等（行首和行中）
       • 清理图片语法：![alt](url) → alt（优先处理）
       • 清理链接语法：[text](url) → text
       • 移除代码块标记：```code``` → code
       • 清理强调语法：**bold** *italic* ~~strikethrough~~ → bold italic strikethrough
       • 处理列表标记：- * + 和数字列表（行首和行中）
       • 移除引用标记：> 开头
       • 移除表格语法：| 分隔符和表格分隔线
       • 移除水平分隔线：--- *** ___
       • 清理独立URL链接：http/https/ftp/www链接
       
    6. 装饰符号删除 (remove_decorative_symbols)
       • 表情符号范围：\U0001F600-\U0001F64F（表情符号）
       • 符号和象形文字：\U0001F300-\U0001F5FF
       • 交通和地图符号：\U0001F680-\U0001F6FF
       • 国旗符号：\U0001F1E0-\U0001F1FF
       • 补充符号：\U0001F900-\U0001F9FF, \U0001FA70-\U0001FAFF
       • 几何形状扩展：\U0001F780-\U0001F7FF
       • 补充箭头：\U0001F800-\U0001F8FF
       • 杂项符号和装饰符号：\U00002600-\U000027BF（星座、棋类、天气等）
       • 几何符号：\U000025A0-\U000025FF
       • 音乐符号：\U0001D100-\U0001D1FF（装饰性音乐符号）
       • 字母类符号：\U00002100-\U0000214F（商标™、电话℡等装饰性符号）
       • 版权符号：©（\u00A9）、®（\u00AE）
       • 变体选择符：\uFE0F（表情符号样式）、\uFE00-\uFE0E
       • 分类原则：处理具有图片/图标特征的符号，保留文本形式的实用符号
     
    【个人隐私信息打码】- 符合数据保护法规的敏感信息处理
    7. 个人信息打码 (mask_personal_info)
       • 手机号打码：1[3-9]\d{9} → 前3位+****+后4位
       • 邮箱打码：user@domain.com → 首字符+***+@domain.com
       • 身份证号打码：18位（前6位+********+后4位，屏蔽出生日期8位）
      
    8. 银行卡号打码 (mask_bank_cards)
       • 支持9种主流银行卡格式：
         - Visa：16位（4开头）、13位（旧版）
         - 万事达：16位（51-55开头）
         - 银联：16-19位（62开头）
         - 美国运通：15位（34/37开头）
         - JCB：16位（3528-3589开头）
         - 大来卡：14位（300-305开头）
         - 大来国际：14位（36开头）
         - Discover：16位（6011开头）
       • 安全打码：保留前4位和后4位，中间用星号替代
       • 适应中文环境，不使用单词边界匹配
       
    【网络隐私信息打码】- 全面的网络安全信息保护
    9. 网络标识符打码 (mask_network_identifiers) [整合功能]
       • MAC地址：AA:BB:CC:DD:EE:FF、AA-BB-CC-DD-EE-FF格式 → [MAC已打码]
       • IP端口组合：IPv4:port、[IPv6]:port → [IP端口已打码]
       • 子网掩码：CIDR格式（x.x.x.x/xx） → [子网掩码已打码]
       • IPv4地址：严格两阶段验证（宽松匹配+严格验证） → [IP已打码]
         - 排除版本号等非IP数字组合
         - 验证每个八位组范围（0-255）和前导零
       • IPv6地址：完整、压缩、IPv4映射格式 → [IPv6已打码]
         - 支持区域标识符（%符号）
         - 严格验证十六进制段和格式
       
    10. 网络凭证打码 (mask_network_credentials)
       • 证书序列号：冒号分隔的十六进制序列 → [证书序列号已打码]
       • 证书指纹：基于上下文关键词识别（thumbprint/fingerprint/cert）
         - SHA1（40位）、SHA256（64位）、SHA384（96位）、SHA512（128位）
       • 密码哈希：MD5（32位）、SHA系列、bcrypt格式 → [密码哈希已打码]
         - 排除证书指纹等其他十六进制内容
         - 基于上下文关键词（hash/md5/sha）识别
        
    【标点符号标准化】
    11. 中英文标点符号转换 (normalize_punctuation)
       • 英文标点转中文：, . ; : ! ? → ，。；：！？
       • 括号转换：() → （）
       • 连字符转换：- → —（破折号）
       • 省略号转换：... → …
       • 引号智能转换："content" → "content"、' → '
    
    【空白字符处理】- 
    12. 空白字符清理 (remove_whitespace_chars)
        • 删除空格、制表符、换行符、回车符等所有空白字符
        • 正则表达式：[ \t\n\r]+
        • 用于文本紧凑化处理，移除格式化空白
        • 执行顺序：作为最后一步执行，确保前面处理产生的空白也被清理
        
    【综合处理接口】- 统一的处理流程和控制机制
    13. 综合预处理 (process_text/preprocess_text)
        • 13个独立开关控制各项功能
        • 优化处理顺序：
          1) 文本标准化：不可见字符清理 → Unicode标准化 → 繁体转简体
          2) 内容格式清理：HTML清理 → Markdown清理 → 装饰符号删除
          3) 隐私信息打码：个人信息 → 银行卡 → 网络标识符 → 网络凭证
          4) 标点符号标准化（倒数第二步）
          5) 空白字符删除（最后一步）
        • 支持CharacterCleanerConfig配置对象和kwargs参数
        • 向后兼容旧版本接口
        
    14. 批量处理 (process_texts)
        • 对文本列表进行批量清洗处理
        • 支持相同的配置参数和处理流程
        
    15. 统计信息管理 (get_stats, reset_stats)
        • 实时统计各类处理操作数量
        • 分类统计：文本标准化、格式清理、隐私打码、网络安全
        • 支持处理效果分析和质量监控
        • 详细统计项：invisible_chars_removed, unicode_normalized, traditional_converted,
          punctuation_normalized, decorative_symbols_removed, html_tags_removed,
          markdown_cleaned, personal_info_masked, bank_cards_masked,
          network_identifiers_masked, network_credentials_masked, network_config_masked,
          ip_addresses_masked
    
    ========== 依赖库 ==========
    • beautifulsoup4: 企业级HTML解析和清理（可选，有回退方案）
    • opencc-python-reimplemented: 高质量繁简体中文转换（可选）
    • re, unicodedata: Python内置正则表达式和Unicode处理
    • common_utils: 安全导入机制
    • config: CharacterCleanerConfig配置类

    """
    
    def __init__(self):
        # 统计信息 - 按新分类架构组织
        self.stats = {
            # 文本标准化处理
            'invisible_chars_removed': 0,
            'unicode_normalized': 0,
            'traditional_converted': 0,
            'punctuation_normalized': 0,
            'decorative_symbols_removed': 0,
            
            # 内容格式清理
            'html_tags_removed': 0,
            'markdown_cleaned': 0,
            
            # 个人隐私信息打码
            'personal_info_masked': 0,
            'bank_cards_masked': 0,
            
            # 网络隐私信息打码 (整合后的统计)
            'network_identifiers_masked': 0,  # 包含MAC、IP端口、IPv4、IPv6、URL
            'network_credentials_masked': 0,
            'network_config_masked': 0,
            'ip_addresses_masked': 0  # 单独统计IP地址数量
        }
        
        # 使用统一的依赖管理器
        self._deps = _deps
    
    # ==================== 网络隐私信息打码 ====================
    
    def mask_network_identifiers(self, text: str) -> str:
        """
        网络标识符打码：MAC地址、IP端口组合、IPv4和IPv6地址、子网掩码、网关地址
        整合所有网络相关标识符和配置的打码功能
        """
        if not text:
            return text
            
        original = text
        identifiers_found = False
        config_found = False
        ip_count = 0
        
        # MAC地址打码 (支持多种格式：AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AABBCCDDEEFF)
        mac_patterns = [
            r'(?i)(?<![0-9a-f])(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}(?![0-9a-f])',  # AA:BB:CC:DD:EE:FF 或 AA-BB-CC-DD-EE-FF
        ]
        
        for pattern in mac_patterns:
            if re.search(pattern, text):
                identifiers_found = True
            text = re.sub(pattern, '[已打码]', text)
        
        # IP端口组合打码 (IPv4:port 和 [IPv6]:port)
        ip_port_patterns = [
            r'(?<!\d)(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d):[1-9]\d{0,4}(?!\d)',  # IPv4:port
            r'\[[0-9a-fA-F:]+\]:[1-9]\d{0,4}(?!\d)',  # [IPv6]:port
        ]
        
        for pattern in ip_port_patterns:
            if re.search(pattern, text):
                identifiers_found = True
            text = re.sub(pattern, '[已打码]', text)
        
        # 子网掩码打码（CIDR格式）- 改进匹配逻辑
        cidr_pattern = r'(?<!\d)(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)/(?:[0-2]?\d|3[0-2])(?!\d)'
        cidr_matches = list(re.finditer(cidr_pattern, text))
        if cidr_matches:
            config_found = True
            # 从后往前替换避免位置偏移
            for match in reversed(cidr_matches):
                text = text[:match.start()] + '[已打码]' + text[match.end():]
        
        # IPv4地址打码 (使用两阶段处理：宽松匹配 + 严格验证)
        # 避免匹配身份证号中的数字序列
        ipv4_candidates = []
        ipv4_pattern_loose = r'(?<!\d)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?!\d)'
        
        for match in re.finditer(ipv4_pattern_loose, text):
            candidate = match.group()
            start, end = match.start(), match.end()
            
            # 严格验证每个候选是否为有效IPv4
            octets = candidate.split('.')
            if len(octets) == 4:
                valid = True
                
                # 检查前后是否有数字或点号（避免匹配版本号的一部分）
                if start > 0:
                    prev_char = text[start-1]
                    if prev_char.isdigit() or prev_char == '.':
                        valid = False
                
                if valid and end < len(text):
                    next_char = text[end]
                    if next_char.isdigit() or next_char == '.':
                        valid = False
                
                # 验证每个八位组的范围和格式
                if valid:
                    # 检查是否可能是身份证号的一部分（连续18位数字）
                    # 向前和向后查看是否有更多数字，如果有则可能是身份证号
                    extended_start = max(0, start - 10)
                    extended_end = min(len(text), end + 10)
                    extended_text = text[extended_start:extended_end]
                    # 如果在扩展范围内发现连续的长数字串，则跳过
                    if re.search(r'\d{15,}', extended_text):
                        valid = False
                    
                    if valid:
                        for octet in octets:
                            try:
                                num = int(octet)
                                # 检查范围和前导零
                                if num > 255 or (len(octet) > 1 and octet[0] == '0'):
                                    valid = False
                                    break
                            except ValueError:
                                valid = False
                                break
                
                if valid:
                    ipv4_candidates.append((candidate, start, end))
        
        # 按位置倒序排序，从后往前替换避免位置偏移
        ipv4_candidates.sort(key=lambda x: x[1], reverse=True)
        
        # 替换有效的IPv4地址
        for ipv4_addr, start, end in ipv4_candidates:
            text = text[:start] + '[已打码]' + text[end:]
            ip_count += 1
            identifiers_found = True
        
        # IPv6地址打码 - 改进匹配逻辑
        potential_candidates = []
        # 使用更精确的IPv6模式，包含完整的地址边界
        ipv6_pattern = r'(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:)*::[0-9a-fA-F:]*|(?:[0-9a-fA-F]{1,4}:)+:(?:[0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})(?:%[0-9a-zA-Z]+)?'
        matches = re.finditer(ipv6_pattern, text, re.IGNORECASE)
        
        for match in matches:
            candidate = match.group()
            potential_candidates.append((candidate, match.start(), match.end()))
        
        # 验证IPv6候选并按位置倒序排序
        valid_candidates = []
        for candidate, start, end in potential_candidates:
            clean_candidate = candidate.split('%')[0] if '%' in candidate else candidate
            if self._is_valid_ipv6(clean_candidate):
                valid_candidates.append((candidate, start, end))
        
        valid_candidates.sort(key=lambda x: x[1], reverse=True)
        
        # 替换IPv6地址
        for ipv6_addr, start, end in valid_candidates:
            text = text[:start] + '[已打码]' + text[end:]
            ip_count += 1
            identifiers_found = True
        
        
        # 更新统计
        if identifiers_found:
            self.stats['network_identifiers_masked'] += 1
        if config_found:
            self.stats['network_config_masked'] += 1
        if ip_count > 0:
            self.stats['ip_addresses_masked'] += ip_count
                      
        return text
    
    def mask_network_credentials(self, text: str) -> str:
        """
        网络凭证打码：按类型分组处理，避免重复匹配
        处理顺序：SHA系列哈希 -> 其他哈希算法 -> 证书相关
        """
        if not text:
            return text
            
        original = text
        credentials_found = False
        
        # 第一组：SHA系列哈希算法（统一处理，包含证书指纹）
        sha_hash_patterns = [
            r'\b[A-Fa-f0-9]{40}\b',   # SHA1 (40位)
            r'\b[A-Fa-f0-9]{56}\b',   # SHA224 (56位)
            r'\b[A-Fa-f0-9]{64}\b',   # SHA256 (64位)
            r'\b[A-Fa-f0-9]{96}\b',   # SHA384 (96位)
            r'\b[A-Fa-f0-9]{128}\b',  # SHA512 (128位)
        ]
        
        for pattern in sha_hash_patterns:
            if re.search(pattern, text):
                credentials_found = True
                text = re.sub(pattern, '[已打码]', text)
                break
        
        # 第二组：其他哈希算法
        if not credentials_found:
            other_hash_patterns = [
                r'\b[a-fA-F0-9]{32}\b',  # MD5 (32位)
                r'\b[A-Fa-f0-9]{72}\b',  # 其他72位指纹
            ]
            
            for pattern in other_hash_patterns:
                matches = re.finditer(pattern, text)
                for match in matches:
                    hash_value = match.group()
                    # 排除证书序列号上下文（包含冒号）
                    start_pos = max(0, match.start() - 10)
                    end_pos = min(len(text), match.end() + 10)
                    context = text[start_pos:end_pos]
                    
                    if ':' not in context:
                        credentials_found = True
                        text = re.sub(re.escape(hash_value), '[已打码]', text)
                        break
                if credentials_found:
                    break
        
        # 第三组：特殊格式哈希（带前缀的）
        if not credentials_found:
            special_format_patterns = [
                r'\$2[aby]\$[0-9]{1,2}\$[./A-Za-z0-9]{22,53}',  # bcrypt
                r'\$6\$[./A-Za-z0-9]{0,16}\$[./A-Za-z0-9]{86}',  # SHA512 crypt
            ]
            
            for pattern in special_format_patterns:
                if re.search(pattern, text):
                    credentials_found = True
                    text = re.sub(pattern, '[已打码]', text)
                    break
        
        # 第四组：证书序列号（冒号分隔格式）
        if not credentials_found:
            cert_serial_patterns = [
                r'(?<![:A-Fa-f0-9])[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){7,15}(?![:A-Fa-f0-9])',
            ]
            
            for pattern in cert_serial_patterns:
                serial_matches = list(re.finditer(pattern, text))
                if serial_matches:
                    credentials_found = True
                    # 从后往前替换避免位置偏移
                    for match in reversed(serial_matches):
                        text = text[:match.start()] + '[已打码]' + text[match.end():]
                    break
        

        if credentials_found:
            self.stats['network_credentials_masked'] += 1
            
        return text
    
    # ==================== 综合处理接口 ====================
    
    def remove_invisible_chars(self, text: str) -> str:
        """
        去除不可见字符
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
            
        original_len = len(text)
        
        # 删除所有不可见字符，但保留常见的空白字符
        preserved_chars = {' ', '\t', '\n', '\r'}
        text = ''.join(
            c for c in text 
            if (unicodedata.category(c) not in ('Cc', 'Cf', 'Cs', 'Co', 'Cn', 'Zs', 'Mn') or c in preserved_chars)
        )
        
        if len(text) != original_len:
            self.stats['invisible_chars_removed'] += 1
            
        return text
    
    def unicode_normalize(self, text: str) -> str:
        """
        Unicode文本标准化
        """
        # 输入类型转换 - 统一处理非字符串输入
        if not isinstance(text, str):
            text = str(text) if text is not None else ""
            
        if not text:
            return text
            
        original = text
        # NFC标准化：组合字符
        text = unicodedata.normalize('NFC', text)
        
        if text != original:
            self.stats['unicode_normalized'] += 1
            
        return text
    
    def convert_traditional_to_simplified(self, text: str) -> str:
        """
        繁体转简体 - 使用统一依赖管理
        """
        if not text:
            return text
            
        if not _deps.has_opencc():
            # 静默跳过，避免重复警告
            return text
        
        original = text
        try:
            converter = _deps.get_opencc_converter()
            text = converter.convert(text)
            if text != original:
                self.stats['traditional_converted'] += 1
        except Exception as e:
            warnings.warn(f"繁简转换失败: {e}")
            
        return text
    
    def remove_html_tags(self, text: str) -> str:
        """
        去除HTML格式 - 使用统一依赖管理
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
            
        original = text
        
        # 使用统一的依赖管理器
        if _deps.has_bs4():
            try:
                # 使用BeautifulSoup解析HTML
                BeautifulSoup = _deps.get_bs4()
                soup = BeautifulSoup(text, 'html.parser')
                text = soup.get_text()
                
                if text != original:
                    self.stats['html_tags_removed'] += 1
                    
                return text
                
            except Exception as e:
                warnings.warn(f"BeautifulSoup HTML清理失败，使用回退方案: {e}")
        
        # 回退方案：正则表达式清理
        text = re.sub(r'<[^>]+>', '', text)
        
        # 处理HTML实体 - 遵循"好品味"原则：统一处理
        html_entities = {
            '&lt;': '<', '&gt;': '>', '&amp;': '&', '&quot;': '"', '&apos;': "'",
            '&nbsp;': ' ', '&copy;': '©', '&reg;': '®', '&trade;': '™'
        }
        for entity, char in html_entities.items():
            text = text.replace(entity, char)
        
        if text != original:
            self.stats['html_tags_removed'] += 1
            
        return text
    
    def remove_markdown_syntax(self, text: str) -> str:
        """
        去除Markdown格式语法
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
            
        original = text
        
        # 去除标题语法 (# ## ### 等) - 只删除标记符号，保留文本和空格
        text = re.sub(r'^#{1,6}\s*', '', text, flags=re.MULTILINE)  # 行首标题
        text = re.sub(r'\s+#{1,6}\s*', ' ', text)  # 行中标题标记
        
        # 去除独立的标题标记（没有文本的情况）
        text = re.sub(r'#{1,6}', '', text)
        
        # 去除图片语法 ![alt](链接) -> alt (必须在链接处理之前)
        text = re.sub(r'!\[([^\]]*)\]\([^\)]*\)', r'\1', text)
        # 清理残留的图片标记（没有链接的情况）
        text = re.sub(r'!\[([^\]]*)\]', r'\1', text)
        
        # 去除链接语法 [文本](链接) -> 文本
        text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text)
        
        # 去除代码块语法 ```语言\n代码``` -> 语言\n代码 (保留语言标识符和内容)
        text = re.sub(r'```([^\n]*)\n([^`]*)```', r'\1\n\2', text, flags=re.DOTALL)
        # 处理没有语言标识符的代码块 ```代码``` -> 代码
        text = re.sub(r'```([^`]*)```', r'\1', text, flags=re.DOTALL)
        
        # 去除行内代码语法 `代码` -> 代码
        text = re.sub(r'`([^`]+)`', r'\1', text)
        
        # 保护所有打码星号 - 必须在所有星号处理之前
        # 通用打码模式：字母数字 + 3个以上星号 + 字母数字（可选）
        # 覆盖：手机号138****1234、银行卡6222********1234、邮箱user***@example.com等
        protected_patterns = []
        placeholder_prefix = "PROTECTEDMASK"
        
        # 通用打码保护模式：
        # 1. 字母数字+3个以上星号+可选字母数字：138****1234, user***
        # 2. 可选字母数字+3个以上星号+字母数字：***1234, ***@example.com
        # 只匹配ASCII字母数字，避免匹配中文等其他字符
        mask_pattern = r'[a-zA-Z0-9]+\*{3,}[a-zA-Z0-9]*|[a-zA-Z0-9]*\*{3,}[a-zA-Z0-9]+'
        def protect_mask(match):
            protected_patterns.append(match.group())
            return f"{placeholder_prefix}{len(protected_patterns)-1}END"
        
        text = re.sub(mask_pattern, protect_mask, text)
        
        # 去除粗体语法 **文本** -> 文本
        text = re.sub(r'\*\*([^*]*)\*\*', r'\1', text)
        
        # 去除粗体语法 __文本__ -> 文本
        text = re.sub(r'__([^_]*)__', r'\1', text)
        
        # 去除斜体语法 *文本* -> 文本
        text = re.sub(r'\*([^*]*)\*', r'\1', text)
        
        # 去除斜体语法 _文本_ -> 文本
        text = re.sub(r'_([^_]*)_', r'\1', text)
        
        # 恢复被保护的打码星号（手机号、银行卡、邮箱等）
        for i, protected_pattern in enumerate(protected_patterns):
            placeholder = f"{placeholder_prefix}{i}END"
            text = text.replace(placeholder, protected_pattern)
        
        # 注意：恢复保护的打码后，不再清理星号，避免误删打码信息
        # 只清理下划线（下划线不用于打码）
        text = re.sub(r'_+', '', text)
        
        # 去除删除线语法 ~~文本~~ -> 文本
        text = re.sub(r'~~([^~]+)~~', r'\1', text)
        
        # 去除列表标记 (- * + 开头) - 只删除标记符号，保留换行
        text = re.sub(r'^[\s]*[-+]\s*', '', text, flags=re.MULTILINE)  # 只清理 - 和 +，不清理 *
        # 清理行中的列表标记
        text = re.sub(r'\s+[-+]\s*', ' ', text)  # 只清理 - 和 +，不清理 *
        # 清理单独的列表标记 - 但完全避免清理星号（可能是打码）
        text = re.sub(r'(?<![a-zA-Z0-9])[-+](?![a-zA-Z0-9])\s*', '', text)  # 只清理 - 和 +
        
        # 去除有序列表标记 (数字. 开头) - 保留换行
        text = re.sub(r'^[\s]*\d+\.\s+', '', text, flags=re.MULTILINE)
        # 清理行中的数字列表标记
        text = re.sub(r'\s+\d+\.\s+', ' ', text)
        
        # 去除引用标记 (> 开头)
        text = re.sub(r'^[\s]*>\s*', '', text, flags=re.MULTILINE)
        
        # 去除表格分隔符
        text = re.sub(r'\|', ' ', text)
        text = re.sub(r'^[\s]*[-:]+[\s]*$', '', text, flags=re.MULTILINE)
        
        # 去除水平分隔线
        text = re.sub(r'^[\s]*[-*_]{3,}[\s]*$', '', text, flags=re.MULTILINE)
        
        # 清理剩余的连续符号
        text = re.sub(r'[-=]{2,}', '', text)
        text = re.sub(r'`{3,}', '', text)
        text = re.sub(r'~{3,}', '', text)
        
        # 清理多余的空格，但保留换行
        text = re.sub(r' +', ' ', text)  # 多个空格合并为一个
        text = re.sub(r'^ +', '', text, flags=re.MULTILINE)  # 删除行首空格
        text = re.sub(r' +$', '', text, flags=re.MULTILINE)  # 删除行尾空格
        
        # 去除独立的URL链接（不在Markdown链接语法中的）
        # 修复：更精确的URL匹配，避免误删除正常文本
        text = re.sub(r'\bhttps?://\S+', '', text)
        text = re.sub(r'\bftp://\S+', '', text)
        text = re.sub(r'\bwww\.\S+\.\S+', '', text)
        
        if text != original:
            self.stats['markdown_cleaned'] += 1
            
        return text
    
    # ==================== 个人隐私信息打码 ====================
    
    def mask_personal_info(self, text: str) -> str:
        """
        个人信息打码：手机号、邮箱、身份证号
        - 手机号：前3位+****+后4位
        - 邮箱：首字母+***@域名
        - 18位身份证：前6位+********+后4位（屏蔽出生日期8位）
        """
        if not text:
            return text
            
        original = text
        
        # 手机号打码 - 前三位和后四位保留，中间星号
        def mask_phone(match):
            phone = match.group()
            return phone[:3] + '****' + phone[-4:]
        
        # 手机号打码，但避免匹配身份证号中的数字序列
        text = re.sub(r'(?<!\d)1[3-9]\d{9}(?!\d)', mask_phone, text)
        
        # 邮箱打码 - 保留第一个字符，其余字符用星号替换
        def mask_email(match):
            email = match.group()
            at_pos = email.find('@')
            if at_pos > 1:
                # 计算需要打码的字符数量（除了第一个字符）
                mask_count = at_pos - 1
                return email[0] + '*' * mask_count + email[at_pos:]
            return email
        
        text = re.sub(r'[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', mask_email, text)
        
        # 身份证号打码 (仅18位) - 屏蔽出生日期8位
        def mask_id_card(match):
            id_num = match.group()
            if len(id_num) == 18:
                # 18位身份证：前6位+8个*+后4位（屏蔽出生日期）
                return id_num[:6] + '********' + id_num[-4:]
            return id_num
        
        # 身份证号打码，确保前后没有数字（避免匹配长数字串的一部分）
        text = re.sub(r'(?<!\d)\d{17}[Xx](?!\d)|(?<!\d)\d{18}(?!\d)', mask_id_card, text)
        
        if text != original:
            self.stats['personal_info_masked'] += 1
            
        return text
    
    def mask_bank_cards(self, text: str) -> str:
        """
        银行卡号打码：支持9种主流银行卡格式
        """
        if not text:
            return text
            
        original = text
        
        # 银行卡号正则表达式（适应中文环境，不使用单词边界）
        bank_card_patterns = [
            r'(?<!\d)4\d{15}(?!\d)',                    # Visa 16位
            r'(?<!\d)4\d{12}(?!\d)',                    # Visa 13位(旧版)
            r'(?<!\d)5[1-5]\d{14}(?!\d)',               # 万事达 16位
            r'(?<!\d)62\d{14,17}(?!\d)',               # 银联 16-19位
            r'(?<!\d)3[47]\d{13}(?!\d)',               # 美国运通 15位
            r'(?<!\d)35(?:2[89]|[3-8]\d)\d{12}(?!\d)', # JCB 16位
            r'(?<!\d)30[0-5]\d{11}(?!\d)',             # 大来卡 14位
            r'(?<!\d)36\d{12}(?!\d)',                  # 大来卡国际版 14位
            r'(?<!\d)6011\d{12}(?!\d)',                # Discover 16位
        ]
        
        # 银行卡号打码：保留前4位和后4位，中间用*替代
        original_text = text
        for pattern in bank_card_patterns:
            text = re.sub(pattern, lambda m: m.group()[:4] + '*' * (len(m.group()) - 8) + m.group()[-4:], text)
            
        if text != original_text:
            self.stats['bank_cards_masked'] += 1
            
        return text
    
    def _is_valid_ipv6(self, addr):
        """验证是否为有效的IPv6地址"""
        try:
            # 移除可选的区域标识符
            if '%' in addr:
                addr = addr.split('%')[0]
            
            # 基本格式检查：必须包含冒号，不能有连续的多个冒号（除了::）
            if ':' not in addr:
                return False
            
            # 检查是否包含无效字符（除了十六进制、冒号、点号）
            valid_chars = set('0123456789abcdefABCDEF:.')
            if not all(c in valid_chars for c in addr):
                return False
            
            # 处理双冒号压缩
            if '::' in addr:
                if addr.count('::') > 1:
                    return False
                parts = addr.split('::')
                left = parts[0].split(':') if parts[0] else []
                right = parts[1].split(':') if parts[1] else []
                
                # 检查混合IPv4格式
                if right and '.' in right[-1]:
                    ipv4_part = right[-1]
                    ipv4_octets = ipv4_part.split('.')
                    if len(ipv4_octets) == 4:
                        for octet in ipv4_octets:
                            if not octet.isdigit() or not 0 <= int(octet) <= 255:
                                return False
                        right = right[:-1] + ['0', '0']  # IPv4占用2个IPv6段
                
                total_segments = len([s for s in left if s]) + len([s for s in right if s])
                if total_segments > 8:
                    return False
            else:
                segments = addr.split(':')
                if len(segments) != 8:
                    return False
                
                # 检查混合IPv4格式
                if '.' in segments[-1]:
                    ipv4_part = segments[-1]
                    ipv4_octets = ipv4_part.split('.')
                    if len(ipv4_octets) != 4:
                        return False
                    for octet in ipv4_octets:
                        if not octet.isdigit() or not 0 <= int(octet) <= 255:
                            return False
                    segments = segments[:-1] + ['0', '0']
                
                left = segments
                right = []
            
            # 验证所有段都是有效的十六进制（1-4位）
            all_segments = left + right
            for segment in all_segments:
                if segment == '':
                    continue
                if len(segment) == 0 or len(segment) > 4:
                    return False
                # 严格检查每个字符是否为有效十六进制
                for c in segment:
                    if c not in '0123456789abcdefABCDEF':
                        return False
                # 额外检查：确保段不为空且是有效的十六进制数
                try:
                    int(segment, 16)
                except ValueError:
                    return False
            
            return True
        except:
            return False
    
    def normalize_punctuation(self, text: str) -> str:
        """
        中英文标点符号转换：将英文标点转换为中文标点
        
        转换规则：
        - 英文逗号、句号、分号、冒号等转为中文对应符号
        - 英文引号转为中文引号（智能处理配对）
        - 英文括号转为中文括号
        - 其他英文标点符号转为中文对应符号
        
        Args:
            text (str): 输入文本
            
        Returns:
            str: 转换后的文本
        """
        if not text:
            return text
            
        original = text
        
        # 先处理多字符的标点符号（避免被单字符规则覆盖）
        text = text.replace('...', '…')  # 英文省略号 -> 中文省略号
        
        # 英文转中文标点符号映射表
        punctuation_map = {
            ',': '，',    # 英文逗号 -> 中文逗号
            '.': '。',    # 英文句号 -> 中文句号
            ';': '；',    # 英文分号 -> 中文分号
            ':': '：',    # 英文冒号 -> 中文冒号
            '?': '？',    # 英文问号 -> 中文问号
            '!': '！',    # 英文感叹号 -> 中文感叹号
            '(': '（',    # 英文左括号 -> 中文左括号
            ')': '）',    # 英文右括号 -> 中文右括号
            '-': '—',    # 英文连字符 -> 中文破折号
        }
        
        # 执行基本标点符号转换
        for english_punct, chinese_punct in punctuation_map.items():
            text = text.replace(english_punct, chinese_punct)
        
        # 智能处理英文引号转换
        # 将成对的英文引号转换为中文引号

        # 处理双引号："内容" -> "内容"
        text = re.sub(r'"([^"]*)"', r'"\1"', text)
        
        # 处理单引号：'内容' -> '内容'
        text = text.replace("'", "'")
        
        # 统计转换次数
        if text != original:
            self.stats['punctuation_normalized'] += 1
            
        return text
    
    def remove_decorative_symbols(self, text: str) -> str:
        """
        删除文本中的装饰符号。
        
        该函数专门处理具有图片/图标特征的符号，基于视觉呈现形式进行分类：
        - 包含标准表情符号 Unicode 范围（U+1F600-U+1F64F, U+1F300-U+1F5FF, U+1F680-U+1F6FF等）
        - 包含杂项符号范围 \U00002630-\U00002647 和 \U00002654-\U000026FF（包含星座、棋类、音乐等符号）
        - 装饰性符号：★☆♠♣♥♦等（文本形式装饰符号）
          注意：这些符号不在标准表情符号 Unicode 范围内，但因其装饰特征
          而被明确列入装饰符号处理范围
        
        Args:
            text (str): 输入文本
            
        Returns:
            str: 删除装饰符号后的文本
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
            
        original = text
        
        # 表情符号的Unicode范围和特定符号（消除重叠范围）
        emoji_pattern = re.compile(
            "["
            "\U0001F600-\U0001F64F"  # 表情符号
            "\U0001F300-\U0001F5FF"  # 符号和象形文字
            "\U0001F680-\U0001F6FF"  # 交通和地图符号
            "\U0001F1E0-\U0001F1FF"  # 国旗
            "\U0001F900-\U0001F9FF"  # 补充符号和象形文字
            "\U0001FA70-\U0001FAFF"  # 扩展符号和象形文字-A
            "\U0001F780-\U0001F7FF"  # 几何形状扩展
            "\U0001F800-\U0001F8FF"  # 补充箭头-C
            "\U00002600-\U000027BF"  # 杂项符号和装饰符号（合并范围）
            "\U00002B00-\U00002BFF"  # 杂项符号和象形文字 
            "\U000025A0-\U000025FF"  # 几何符号
            "\U0001D100-\U0001D1FF"  # 音乐符号（装饰性）
            "\U00002100-\U0000214F"  # 字母类符号（包含版权符号™等）
            "\u00A9"                 # © 版权符号
            "\u00AE"                 # ® 注册商标
            "\uFE0F"                 # 变体选择符-16（表情符号样式）
            "\uFE00-\uFE0E"          # 其他变体选择符
            "]+", flags=re.UNICODE)
        
        # 删除装饰符号，但不处理空格
        text = emoji_pattern.sub('', text)
        
        if text != original:
            self.stats['decorative_symbols_removed'] += 1
            
        return text
    

    
    def remove_whitespace_chars(self, text: str) -> str:
        """
        删除空白字符：删除空格、回车符、制表符、换行符
        
        Args:
            text: 待处理的文本
            
        Returns:
            处理后的文本（删除所有空白字符）
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
        
        # 删除所有空白字符：空格、制表符、换行符、回车符等
        # 使用简单直接的方法，删除所有空白字符
        text = ''.join(c for c in text if not c.isspace())
        
        return text
    
    def preprocess_text(self, text: str, config: CharacterCleanerConfig = None, **kwargs) -> str:
        """
        综合文本预处理：统一的文本清洗处理接口
        
        Args:
            text: 待处理的文本
            config: 清洗配置对象，如果为None则使用默认配置
            **kwargs: 兼容旧版本的参数，会被config覆盖
            
        Returns:
            处理后的文本
        """
        return self.process_text(text, config, **kwargs)
    
    def process_text(self, text: str, config: Union[CharacterCleanerConfig, None] = None, **kwargs) -> str:
        """
        综合文本预处理：统一的文本清洗处理接口（向后兼容方法）
        
        Args:
            text: 待处理的文本
            config: 清洗配置对象，如果为None则使用默认配置
            **kwargs: 兼容旧版本的参数，会被config覆盖
            
        Returns:
            处理后的文本
        """
        # 统一输入处理 - 好品味原则：消除边界情况
        if text is None:
            return ""
        if not isinstance(text, str):
            text = str(text)
        if not text:
            return text
        
        # 处理配置参数
        if config is not None:
            params = config.to_dict()
        else:
            # 兼容旧接口，使用默认值（按功能执行顺序排列）
            params = {
                # ========== 文本标准化处理 ==========
                'enable_invisible_chars_removal': kwargs.get('enable_invisible_chars_removal', False),
                'enable_unicode_normalize': kwargs.get('enable_unicode_normalize', False),
                'enable_traditional_convert': kwargs.get('enable_traditional_convert', False),
                # ========== 内容格式清理 ==========
                'enable_html_removal': kwargs.get('enable_html_removal', False),
                'enable_markdown_removal': kwargs.get('enable_markdown_removal', False),
                'enable_decorative_symbols_removal': kwargs.get('enable_decorative_symbols_removal', False),
                # ========== 个人隐私信息打码 ==========
                'enable_personal_info_masking': kwargs.get('enable_personal_info_masking', False),
                'enable_bank_card_masking': kwargs.get('enable_bank_card_masking', False),
                # ========== 网络隐私信息打码 ==========
                'enable_network_identifiers_masking': kwargs.get('enable_network_identifiers_masking', False),
                'enable_network_credentials_masking': kwargs.get('enable_network_credentials_masking', False),
                # ========== 标点符号标准化 ==========
                'enable_punctuation_normalize': kwargs.get('enable_punctuation_normalize', False),
                # ========== 空白字符删除 ==========
                'enable_whitespace_removal': kwargs.get('enable_whitespace_removal', False)
            }
        
        # ========== 文本标准化处理 ==========
        # 1. 去除不可见字符
        if params['enable_invisible_chars_removal']:
            text = self.remove_invisible_chars(text)
        
        # 2. Unicode标准化
        if params['enable_unicode_normalize']:
            text = self.unicode_normalize(text)
        
        # 3. 繁体转简体
        if params['enable_traditional_convert']:
            text = self.convert_traditional_to_simplified(text)
        
        # ========== 内容格式清理 ==========
        # 4. 去除HTML标签（在标点符号转换之前）
        if params['enable_html_removal']:
            text = self.remove_html_tags(text)
        
        # 5. 去除Markdown格式
        if params['enable_markdown_removal']:
            text = self.remove_markdown_syntax(text)
        
        # 6. 删除装饰符号
        if params['enable_decorative_symbols_removal']:
            text = self.remove_decorative_symbols(text)
        
        # ========== 个人隐私信息打码 ==========
        # 7. 个人信息打码
        if params['enable_personal_info_masking']:
            text = self.mask_personal_info(text)
        
        # 8. 银行卡号打码
        if params['enable_bank_card_masking']:
            text = self.mask_bank_cards(text)
        
        # ========== 网络隐私信息打码 ==========
        # 9. 网络标识符打码 (包含MAC地址、IP端口、IPv4、IPv6、子网掩码、网关地址)
        if params['enable_network_identifiers_masking']:
            text = self.mask_network_identifiers(text)
        
        # 10. 网络凭证打码
        if params['enable_network_credentials_masking']:
            text = self.mask_network_credentials(text)
        
        # ========== 标点符号标准化 ==========
        # 11. 标点符号转换（倒数第二步，在空白字符删除之前）
        if params['enable_punctuation_normalize']:
            text = self.normalize_punctuation(text)
        
        # ========== 空白字符删除 ==========
        # 12. 删除空白字符
        if params['enable_whitespace_removal']:
            text = self.remove_whitespace_chars(text)
        
        return text
    
    def process_texts(self, texts: List[str], config: CharacterCleanerConfig = None, **kwargs) -> List[str]:
        """
        批量文本预处理：对文本列表进行批量清洗处理
        
        Args:
            texts: 待处理的文本列表
            config: 清洗配置对象，如果为None则使用默认配置
            **kwargs: 兼容旧版本的参数，会被config覆盖
            
        Returns:
            处理后的文本列表
        """
        if not texts:
            return []
            
        processed_texts = []
        for text in texts:
            processed_text = self.process_text(text, config, **kwargs)
            processed_texts.append(processed_text)
        
        return processed_texts
    
    def get_stats(self) -> Dict[str, int]:
        """
        获取处理统计信息
        """
        return self.stats.copy()
    
    def reset_stats(self):
        """
        重置统计信息
        """
        for key in self.stats:
            self.stats[key] = 0